package suncertify.contractors;

import java.io.Serializable;
import java.text.NumberFormat;

import suncertify.application.Application;
import suncertify.application.Strings;
import suncertify.db.DBAccess;
import suncertify.db.Data;
import suncertify.db.DataValidator;

/**
 * This class represents a contractor which is the basic entity of the application's data and provides an object
 * oriented representation of the a contractor data string array used in the {@link DBAccess} interface.
 * 
 * <p>
 * Usually this class is instantiated when a record is read from the database, the constructor of this class requires an
 * <code>id</code> which corresponds to the <code>recNo</code> in the <code>DBAccess</code> interface and a
 * <code>String[]</code> which corresponds to the <code>data</code> parameter in the <code>DBAccess</code> interface.
 * 
 * <p>
 * The class provides a <code>getProperty()</code> method for each contractor property e.g. <code>getName()</code>, the
 * class also provides methods that format specific contractor properties to be displayed in the user interface
 * appropriately e.g. the {@link #getRateAsDisplayString()}
 * 
 * <p>
 * The class implements the Comparable interface which makes it sortable.
 * 
 * @see Serializable
 * @see Comparable
 */
public class Contractor implements Serializable, Comparable<Contractor> {

    /**
     * Automatic generated serialization version ID.
     */
    private static final long serialVersionUID = 4685734896445426194L;

    /**
     * Represents the contractor's ID.
     */
    private long id = -1;

    /**
     * Holds the contractor's name.
     */
    private String name;

    /**
     * Holds the contractor's location.
     */
    private String location;

    /**
     * Holds the contractor's specialties.
     */
    private String specialties;

    /**
     * Holds the contractor's size.
     */
    private int size;

    /**
     * Holds the contractor's rate.
     */
    private float rate;

    /**
     * Holds the contractor's owner.
     */
    private int owner;

    /**
     * Constructs a <code>Contractor</code> object. The <code>id</code> and <code>recordData</code> are intended to
     * correspond to the <code>long recNo</code> and <code>String[] data</code> parameters in {@link DBAccess}
     * interface.
     * 
     * <p>
     * The constructor validates the input parameters according to the rules explained the throws section below. If the
     * parameters are valid then the <code>id</code> and all values from the <code>String[]</code> parameter are copied
     * into corresponding class member variables.
     * 
     * @param id
     *            The unique ID of the contractor in the database. This value corresponds to the record position in the
     *            database file. See <code>recNo</code> in {@link DBAccess}. The value of this parameter is not
     *            validated against the database e.g. if the database consists of 100 records (i.e. recNo=0 to recNo=99)
     *            then this parameter can have the value 120.
     * @param recordData
     *            A string array of the contractor's property values. This parameter is equivalent to the
     *            <code>data</code> parameter in the {@link DBAccess} interface. The length of the array must be
     *            equivalent to {@link Data#FIELD_COUNT}.
     * 
     * @throws IllegalArgumentException
     *             when:
     *             <ol>
     *             <li>The <code>id</code> is negative.</li> <li>The <code>recordData</code> is <code>null</code>.</li>
     *             <li>The <code>recordData.length()</code> does not equal the {@link Data#FIELD_COUNT}</li> <li>The
     *             {@link DataValidator#isNameValid(String)} returns false.</li> <li>The
     *             {@link DataValidator#isLocationValid(String)} returns false.</li> <li>The
     *             {@link DataValidator#isSpecialtiesValid(String)} returns false.</li> <li>The
     *             {@link DataValidator#isSizeValid(String)} returns false.</li> <li>The
     *             {@link DataValidator#isRateValid(String)} returns false.</li> <li>The contractors 'owner' property is
     *             a none empty string and the {@link DataValidator#isOwnerValid(String)} returns false.</li>
     *             </ol>
     */
    public Contractor(long id, String[] recordData) {

	if (id < 0) {
	    throw new IllegalArgumentException("Contractor ID must be > 0!");
	}

	this.isDataValid(recordData);

	this.id = id;
	this.name = recordData[Data.FIELD_INDEX_NAME].trim();
	this.location = recordData[Data.FIELD_INDEX_LOCATION].trim();
	this.specialties = recordData[Data.FIELD_INDEX_SPECIALTIES].trim();
	this.size = Integer.parseInt(recordData[Data.FIELD_INDEX_SIZE].trim());
	this.rate = Float.parseFloat(recordData[Data.FIELD_INDEX_RATE].trim().substring(1));

	if (recordData[Data.FIELD_INDEX_OWNER] == null || recordData[Data.FIELD_INDEX_OWNER].trim().isEmpty()) {
	    this.owner = 0;
	} else {
	    this.owner = Integer.parseInt(recordData[Data.FIELD_INDEX_OWNER].trim());
	}
    }

    /**
     * Validates the <code>recordData</code> values. If the <code>recordData</code> is valid then the function simply
     * returns and throws no exceptions.
     * 
     * @param recordData
     *            A string array of the contractor's property values. This parameter is equivalent to the
     *            <code>data</code> parameter in the {@link DBAccess} interface. The length of the array must be
     *            equivalent to {@link Data#FIELD_COUNT}.
     * 
     * @throws IllegalArgumentException
     *             when:
     *             <ol>
     *             <li>The <code>recordData</code> is <code>null</code>.</li>
     *             <li>The <code>recordData.length()</code> does not equal the {@link Data#FIELD_COUNT}</li>
     *             <li>The {@link DataValidator#isNameValid(String)} returns false.</li>
     *             <li>The {@link DataValidator#isLocationValid(String)} returns false.</li>
     *             <li>The {@link DataValidator#isSpecialtiesValid(String)} returns false.</li>
     *             <li>The {@link DataValidator#isSizeValid(String)} returns false.</li>
     *             <li>The {@link DataValidator#isRateValid(String)} returns false.</li>
     *             <li>The contractors 'owner' property is a none empty string and the
     *             {@link DataValidator#isOwnerValid(String)} returns false.</li>
     *             </ol>
     */
    private void isDataValid(String[] recordData) {

	if (recordData == null) {
	    throw new IllegalArgumentException("Contractor recordData cannot be null!");
	}

	if (recordData.length != Data.FIELD_COUNT) {
	    throw new IllegalArgumentException(Strings.getMessage(Strings.MSG_RECORD_INVALID_FIELD_COUNT, Integer
		    .toString(Data.FIELD_COUNT)));
	}

	if (!DataValidator.isNameValid(recordData[Data.FIELD_INDEX_NAME])) {
	    throw new IllegalArgumentException(Strings.MSG_INVALID_FIELD_NAME);
	}

	if (!DataValidator.isLocationValid(recordData[Data.FIELD_INDEX_LOCATION])) {
	    throw new IllegalArgumentException(Strings.MSG_INVALID_FIELD_LOCATION);
	}

	if (!DataValidator.isSpecialtiesValid(recordData[Data.FIELD_INDEX_SPECIALTIES])) {

	    throw new IllegalArgumentException(Strings.MSG_INVALID_FIELD_SPECIALTIES);
	}

	if (!DataValidator.isSizeValid(recordData[Data.FIELD_INDEX_SIZE])) {

	    throw new IllegalArgumentException(Strings.MSG_INVALID_FIELD_SIZE);
	}

	if (!DataValidator.isRateValid(recordData[Data.FIELD_INDEX_RATE])) {

	    throw new IllegalArgumentException(Strings.MSG_INVALID_FIELD_RATE);
	}

	if (recordData[Data.FIELD_INDEX_OWNER] != null) {

	    if (recordData[Data.FIELD_INDEX_OWNER].trim().isEmpty())
		return;

	    if (!DataValidator.isOwnerValid(recordData[Data.FIELD_INDEX_OWNER])) {

		throw new IllegalArgumentException(Strings.MSG_INVALID_FIELD_OWNER);
	    }
	}
    }

    /**
     * Returns the maximum value allowed for the 'owner' property.
     * 
     * @return {@link Data#FIELD_OWNER_MAXVALUE}
     */
    public static int GetOwnerMaxValue() {
	return Data.FIELD_OWNER_MAXVALUE;
    }

    /**
     * Returns the contractor property value by the given index. Each contractor property has a unique index value that
     * starts with 0, e.g. name property has index 0, location property has index 1 and so on. The
     * <code>Contractor</code> property indexes correspond to the <code>data</code> string array indexes in the
     * {@link DBAccess} interface.
     * 
     * @param index
     *            The contractor property index.
     * @return The property value. e.g. <code>index=0</code> invokes {@link #getName()} and <code>index=1</code> invokes
     *         {@link #getLocation()}
     * 
     * @throws IllegalArgumentException
     *             if the <code>index</code> parameter does not equal the below listed indexes.
     * 
     * @see Data#FIELD_INDEX_NAME
     * @see Data#FIELD_INDEX_LOCATION
     * @see Data#FIELD_INDEX_SPECIALTIES
     * @see Data#FIELD_INDEX_SIZE
     * @see Data#FIELD_INDEX_RATE
     * @see Data#FIELD_INDEX_OWNER
     * 
     */
    public String getDisplayValueByIndex(int index) {

	switch (index) {

	case Data.FIELD_INDEX_NAME:
	    return this.getName();

	case Data.FIELD_INDEX_LOCATION:
	    return this.getLocation();

	case Data.FIELD_INDEX_SPECIALTIES:
	    return this.getSpecialties();

	case Data.FIELD_INDEX_SIZE:
	    return this.getSizeAsDisplayString();

	case Data.FIELD_INDEX_RATE:
	    return this.getRateAsDisplayString();

	case Data.FIELD_INDEX_OWNER:
	    return this.getOwnerAsDisplayString();

	default:
	    throw new IllegalArgumentException(Strings.getMessage(Strings.MSG_INVALID_CONTRACTOR_PROPERTY_INDEX,
		    Integer.toString(index)));
	}
    }

    /**
     * Returns the contractor's name.
     * 
     * @return Contractor's name.
     */
    public String getName() {
	return this.name;
    }

    /**
     * Returns the contractor's location or city.
     * 
     * @return Contractor's location or city.
     */
    public String getLocation() {
	return this.location;
    }

    /**
     * Returns the contractor's specialties.
     * 
     * @return Contractor's specialties.
     */
    public String getSpecialties() {
	return this.specialties;
    }

    /**
     * Returns the contractor's size (number of employees).
     * 
     * @return contractor's size.
     */
    public int getSize() {
	return this.size;
    }

    /**
     * Returns the contractor's size as a <code>String</code>. If the size is 0 then null is returned else the size is
     * converted to string using {@link Integer#toString()}.
     * 
     * @return contractor's size as a <code>String</code>.
     */
    public String getSizeAsDatabaseString() {

	if (this.size == 0) {
	    return null;
	} else {
	    return Integer.toString(this.size);
	}
    }

    /**
     * Returns the contractor's size as <code>String</code>. If size is zero then empty string is returned else the size
     * value is formated with {@link NumberFormat#getIntegerInstance(java.util.Locale)}.
     * 
     * @return The size as string.
     */
    public String getSizeAsDisplayString() {

	if (this.size == 0) {
	    return "";
	} else {
	    NumberFormat nf = NumberFormat.getIntegerInstance(Application.getApplicationLocale());
	    return nf.format(this.size);
	}
    }

    /**
     * Returns the contractor's rate as a currency formatted string. Rate values are stored like "$1200.50" in the
     * application's database, this function formats the rate value in the application's locale as "$1,200.50".
     * 
     * @return contractor's rate as a formatted currency string.
     * 
     * @see Application#getApplicationLocale()
     * @see NumberFormat
     */
    public String getRateAsDisplayString() {
	NumberFormat nf = NumberFormat.getCurrencyInstance(Application.getApplicationLocale());
	return nf.format(this.rate);
    }

    /**
     * Returns the contractor's rate as a string. The format of the string is the same format returned by
     * {@link #getRateAsDisplayString()} except that the thousand separator character is omitted, e.g. "$1000.50" or
     * "$40.00"
     * 
     * @return The contractor's rate value as string.
     */
    public String getRateAsDatabaseString() {

	String s = this.getRateAsDisplayString();

	// remove the decimal separator character(s)
	String pattern = "\\" + Application.getLocaleDecimalSeparator();

	s = s.replaceAll(pattern, "");

	return s;
    }

    /**
     * Returns the contractor's owner.
     * 
     * @return The contractor's owner.
     */
    public long getOwner() {
	return this.owner;
    }

    /**
     * Determines if the contractor is booked or not.
     * 
     * @return Returns true if the contractor is booked to a customer, returns false otherwise.
     */
    public boolean isBooked() {
	return this.owner > 0;
    }

    /**
     * Returns the contractors owner as a <code>String</code>. If the owner is not defined then an empty string is
     * returned. The return value is formated with {@link NumberFormat#getIntegerInstance(java.util.Locale)}.
     * 
     * @return contractor owner.
     */
    public String getOwnerAsDisplayString() {

	if (this.owner == 0) {
	    return "";
	} else {
	    NumberFormat nf = NumberFormat.getIntegerInstance(Application.getApplicationLocale());
	    return nf.format(this.owner);
	}
    }

    /**
     * Returns the contractor's owner as a string. If the owner is not defined then a null is returned else the owner
     * value is converted to string using {@link Integer#toString()}.
     * 
     * @return The contractor's owner.
     */
    public String getOwnerAsDatabaseString() {

	if (this.owner == 0) {
	    return null;
	} else {
	    return Integer.toString(this.owner);
	}
    }

    /**
     * Sets the contractor's owner property to the specified value. Note, the <code>ownerValue</code> is not validated,
     * it is simply stored in the {@link #owner} class variable.
     * 
     * @param ownerValue
     *            The contractor's owner value.
     */
    public void setOwner(int ownerValue) {
	this.owner = ownerValue;
    }

    /**
     * Clears the contractor's booking and sets the owner field to zero.
     */
    public void removeOwner() {
	this.owner = 0;
    }

    /**
     * Returns the contractor's ID.
     * 
     * @return The contractor's ID.
     */
    public long getID() {
	return this.id;
    }

    /**
     * Overrides the {@link Object#toString()} function. Returns a string representation of the contractor, the
     * contractor properties like name, location, specialties, size, rate and owner are formatted in the
     * {@link Strings#MSG_CONTRACTOR_STRING} string.
     * 
     * @return A <code>String</code> that represents the contractor.
     * 
     */
    public String toString() {
	return Strings.getMessage(Strings.MSG_CONTRACTOR_STRING, this.name, this.location, this.specialties, this
		.getSizeAsDisplayString(), this.getRateAsDisplayString(), this.getOwnerAsDisplayString());
    }

    /**
     * Returns a <code>String</code> array that represents the contractor, each property of the contractor e.g. 'name'
     * and 'location' correspond to one String in the string array. The size of the string array is defined by
     * {@link Data#FIELD_COUNT}. The order of the values in the string array is defined by the order of the fields in
     * the database. The index of the contractor properties is defined by the <code>Data.FIELD_INDEX_</code> constants
     * in the <@link Data> class.
     * 
     * @return the contractor as string array.
     */
    public String[] toStringArray() {

	String[] stringArray = new String[Data.FIELD_COUNT];

	stringArray[Data.FIELD_INDEX_NAME] = this.getName();
	stringArray[Data.FIELD_INDEX_LOCATION] = this.getLocation();
	stringArray[Data.FIELD_INDEX_SPECIALTIES] = this.getSpecialties();
	stringArray[Data.FIELD_INDEX_SIZE] = this.getSizeAsDatabaseString();
	stringArray[Data.FIELD_INDEX_RATE] = this.getRateAsDatabaseString();
	stringArray[Data.FIELD_INDEX_OWNER] = this.getOwnerAsDatabaseString();

	return stringArray;
    }

    /**
     * Copies all property values except the 'owner' property from the current contractor to a new instance of the
     * <code>Contractor</code> class. The owner property of the new contractor is set to the provided <code>owner</code>
     * 
     * @param owner
     *            The owner value for the new contractor object.
     * @return New <code>Contractor</code> object.
     */
    public Contractor copyWithOwner(int owner) {

	Contractor contractor = new Contractor(this.id, this.toStringArray());
	contractor.setOwner(owner);
	return contractor;
    }

    /**
     * Clones the current contractor instance and returns a new Contractor object. The returned contractor is identical
     * to the current contractor except that its booking is cleared. Calls {@link #copyWithOwner(int)} with owner 0
     * owner value.
     * 
     * @return A new instance of the Contractor class.
     */
    public Contractor copyWithEmptyOwner() {

	return this.copyWithOwner(0);
    }

    /**
     * Returns a <code>String[]</code> off all contractor property names. The length of the <code>String[]</code> is
     * determined by {@link Data#FIELD_COUNT}. The order of the property names is the same as the fields order in the
     * database.
     * 
     * @return <code>String[]</code> of the contractor property names.
     * 
     */
    public static String[] getPropertyNames() {

	return new String[] { Strings.CAP_CONTRACTOR_PROPERTY_NAME, Strings.CAP_CONTRACTOR_PROPERTY_CITY,
		Strings.CAP_CONTRACTOR_PROPERTY_SPECIALTIES, Strings.CAP_CONTRACTOR_PROPERTY_STAFF_NUMBER,
		Strings.CAP_CONTRACTOR_PROPERTY_CHARGE, Strings.CAP_CONTRACTOR_PROPERTY_OWNER };
    }

    /**
     * Overrides the {@link Object#equals(Object)} method and provides a way to compare <code>Contractor</code> objects.
     * 
     * @param obj
     *            The contractor instance to be compared with the current contractor object.
     * @return True if the ID of both objects are the same. Returns false if the IDs are different or the
     *         <code>obj</code> parameter is not of type <code>Contractor</code>.
     * 
     */
    public boolean equals(Object obj) {

	if (!(obj instanceof Contractor)) {

	    return false;
	}

	Contractor otherContractor = (Contractor) obj;

	return this.id == otherContractor.getID();
    }

    /**
     * Validates the provided owner value. This function delegates the validation ot the
     * {@link DataValidator#isOwnerValid(String)} function.
     * 
     * @param owner
     *            The owner value to be validated.
     * @return True if the owner value is valid, false otherwise.
     */
    public static boolean isOwnerValid(int owner) {

	return DataValidator.isOwnerValid(Integer.toString(owner));
    }

    /**
     * Provides a way to compare two contractors. See {@link Comparable} for more details.
     * 
     * @param contractor
     *            The contractor instance to be compared.
     * @return See {@link Comparable} for more details.
     * 
     */
    public int compareTo(Contractor contractor) {

	return this.name.compareTo(contractor.getName());
    }
}
